Skip to content

Conversation

@patrick91
Copy link
Contributor

No description provided.

@patrick91 patrick91 added the feature New feature or request label Oct 9, 2025
@patrick91 patrick91 force-pushed the 10-09-_automatically_map_dictionaries_to_json branch 7 times, most recently from 1fa6323 to 917808f Compare October 9, 2025 15:30
@patrick91 patrick91 force-pushed the 10-09-_automatically_map_dictionaries_to_json branch 2 times, most recently from c4312f9 to 8d71048 Compare October 9, 2025 15:38
Copy link
Member

@YuriiMotov YuriiMotov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have such native support for dict!

As an improvement idea, we can support automatic mapping to JSON for pydantic.Json type, BaseModel, and also for list (we can map it to JSON by default, but it will still be possible to change mapping to sqlalchemy.dialects.postgresql.ARRAY using the sa_type parameter).

One potential problem we should probably think about:
If we define our field as dict[str, int] it will not set any DB constraints and it will be possible to have invalid data in the DB (e.g. {"param": "text"}).

Code example in the details
from sqlmodel import Field, Session, SQLModel, create_engine, text


class MyObject(SQLModel, table=True):
    id: int = Field(primary_key=True)
    params: dict[str, int]

engine = create_engine("sqlite:///")
SQLModel.metadata.create_all(engine)


# Imagine this was added before, probably by external
with Session(engine) as session:
    session.exec(
        text('INSERT INTO myobject (id, params) VALUES (1, \'{"param": "text"}\')')
    )
    session.commit()

with Session(engine) as session:
    obj = session.get(MyObject, 1)
    assert isinstance(obj.params, dict)

    assert isinstance(obj.params["param"], int), type(obj.params["param"])
    # AssertionError: <class 'str'>

When we fetch such row, SQLModel will not complain that type is wrong, but users may have errors in their code if they assume that fetched data has valid type.
Such errors might be tricky to debug - static type checkers will not show any warnings.

As a solution, we can probably go extra mile and implement a new type (using [TypeDecorator](https://docs.sqlalchemy.org/en/20/core/custom_types.html)) that will not just convert data to JSON, but also ensure data has valid type.

PS: ready to help with it

Comment on lines -15 to -22
def test_type_dict_breaks() -> None:
with pytest.raises(ValueError):

class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
tags: Dict[str, Any]


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will still fail with Pydantic V1.
I suggest we keep with test, but just mark it with needs_pydanticv2

Comment on lines +60 to +62
This fixes the original error:
ValueError: <class 'app.models.NeonMetadata'> has no matching SQLAlchemy type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This fixes the original error:
ValueError: <class 'app.models.NeonMetadata'> has no matching SQLAlchemy type

I think we don't need this part here

from .conftest import needs_pydanticv2


def test_type_mappings(clear_sqlmodel):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand, the idea of this test module is to test all type mapping in one module.
Then we should probably add tests for:

  • Enum
  • ipaddress.IPv4Address
  • ipaddress.IPv4Network
  • ipaddress.IPv6Address
  • ipaddress.IPv6Network
  • Path
  • EmailStr
  • timedelta
  • Decimal
  • uuid.UUID

@patrick91
Copy link
Contributor Author

@YuriiMotov happy for you to add support for TypeDecorator, maybe we can ask @tiangolo if he has any opinions first? 😊

@patrick91
Copy link
Contributor Author

@YuriiMotov also doesn't seems like SQLModel does validation for any incoming data? it would be a different behaviour from the rest?

@YuriiMotov
Copy link
Member

YuriiMotov commented Oct 30, 2025

@YuriiMotov also doesn't seems like SQLModel does validation for any incoming data? it would be a different behaviour from the rest?

It's true, but..
Currently, in most cases we will have validation on DB side (since we specify the types of columns and other constraints). So, assuming DB schema is in line with models, we don't need to validate it on SQLModel side (assuming we are using not SQLite, but more advanced DB).
Having Dict -> JSON mapper by default will increase the number of cases when data is not validated by any side..

@YuriiMotov
Copy link
Member

As an idea: we can add such validation for dict - JSON mapping, but document how it can be disabled.
So, it will be enabled by default, but if it's not needed in particular project, developers will be able to disable it and avoid overheads.

For example

class MyModel(SQLModel, table=True):
    f1: dict[str, int]  # DB data will be validated
    f2: dict[str, int] = Field(sa_type=JSON)  # DB data will not be validated

@nkrishnaswami
Copy link

convert data to JSON, but also ensure data has valid type

I've had good luck using Pydantic's TypeAdapter in type decorators for this; it would let you support Pydantic types as well as builtins like Dict[str, int].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants